{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Loading and normalizing CIFAR10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import torchvision\n",
    "import torchvision.transforms as transforms"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "transform = transforms.Compose([\n",
    "    # 将一个 PIL Image 或者 numpy.ndarray（H x W x C）转化为 torch.Tensor（C x H x W），\n",
    "    # 并且归一化到 [0, 1]。这一步是为了把图像转化为 PyTorch 可以处理的数据类型\n",
    "    transforms.ToTensor(),\n",
    "    # Normalize 这个函数是进行标准化处理。它接收两个参数：一个是均值，一个是标准差。\n",
    "    # 这里的 (0.5, 0.5, 0.5) 表示 RGB 三个通道的均值和标准差。\n",
    "    # 对每个通道进行如下操作： image = (image - mean) / std,\n",
    "    # 假设原来的像素值是 [0, 1]，那么这个操作之后的像素值就会变成 [-1, 1],\n",
    "    # 这个操作可以使得模型训练过程中的数值更稳定。\n",
    "    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n",
    "])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# CIFAR-10 是一个常用的图像分类数据集，包含 60000 张 32x32 的彩色图片，有 10 个类别。\n",
    "\n",
    "trainset = torchvision.datasets.CIFAR10(root='./data', # 指定了数据集的下载路径\n",
    "                                        train=True, # 表示加载的是训练集\n",
    "                                        download=True, # 表示如果数据集没有在指定路径下找到，那么就下载数据集。如果数据集已经存在，那么这个参数没有作用。\n",
    "                                        transform=transform # 指定了一个数据预处理的函数\n",
    "                                        )\n",
    "# 创建一个数据加载器，这个加载器可以在训练模型时批量加载数据。\n",
    "trainloader = torch.utils.data.DataLoader(trainset, # 是你要加载的数据集\n",
    "                                          batch_size=4, # 指定每个 batch 的大小，也就是每次模型训练时输入的数据量。\n",
    "                                          shuffle=True, # 指在每个 epoch 开始时，要不要把数据打乱。这在训练模型时是一种常用的方式，可以增强模型的泛化能力。\n",
    "                                          num_workers=12 # 指定使用多少个子进程来加载数据\n",
    "                                          )\n",
    "\n",
    "testset = torchvision.datasets.CIFAR10(root='./data',\n",
    "                                       train=False,\n",
    "                                       download=True,\n",
    "                                       transform=transform)\n",
    "testloader = torch.utils.data.DataLoader(testset,\n",
    "                                         batch_size=4,\n",
    "                                         shuffle=False,\n",
    "                                         num_workers=12)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 10个类别\n",
    "classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse',\n",
    "           'ship', 'truck')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "def imshow(img):\n",
    "    # 显示一个图像,这个图像是一个PyTorch的Tensor，具有3个维度，分别代表颜色通道、高度和宽度 (C, H, W)\n",
    "    img = img / 2 + 0.5  # 对图像数据进行反标准化, 标准化后的图像的像素值是在[-1, 1]之间, 反标准化就是将这些值再变回原来的[0, 1]区间。\n",
    "    npimg = img.numpy() # 将 Tensor 转化为 NumPy 数组\n",
    "    plt.imshow(np.transpose(npimg, (1, 2, 0))) # 使用了np.transpose方法对图像的维度进行了调换，Matplotlib希望的输入是（H, W, C）形式的，所以需要进行转换。\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 创建了一个迭代器，这个迭代器可以从trainloader中一次获取一个batch的数据。\n",
    "dataiter = iter(trainloader)\n",
    "# 使用next函数从dataiter中获取了一个batch的数据，这个batch包含了4张图像（因为batch_size=4）以及这些图像对应的标签。\n",
    "images, labels = next(dataiter)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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XR0Fg0GaPn06/cu//AskxbqQ+ue1116jm266iaqqqsjlctHTTz8N8tH0RXd3N91xxx0UCoUoEonQXXfdRQMDAzm8izNHtv5JJpP0zW9+kxYuXEj5+flUVVVFd955J508eRLOcT73z5hRZyGPP/648vl86he/+IV677331N/8zd+oSCSi2traJrppOeX6669XjzzyiNq9e7dqaGhQn/jEJ1Rtba0aGBhwjvnqV7+qampq1IYNG9Tbb7+tVq5cqS699NIJbPXEsG3bNjV16lR10UUXqa9//evO5xdy/3R3d6spU6aoL37xi2rr1q3q8OHD6oUXXlAHDx50jrn//vtVOBxWTz/9tNq5c6f61Kc+perq6tTw8PAEtjw33HfffaqkpEQ9++yz6siRI+qJJ55QBQUF6kc/+pFzzIXUP3/605/Ud77zHfXkk08qIlJPPfUUyEfTFzfccIO6+OKL1ZYtW9Trr7+uZsyYoW6//fYc38mZIVv/RKNRtWrVKvWb3/xG7du3T23evFktX75cLVmyBM5xPvfPWDkrFx/Lly9Xa9ascerpdFpVVVWp9evXT2CrJp729nZFRGrjxo1KqfdfeK/Xq5544gnnmL179yoiUps3b56oZuac/v5+NXPmTPXiiy+qK6+80ll8XOj9881vflNdfvnlI8pt21YVFRXq3//9353PotGo8vv96r//+79z0cQJ5ZOf/KT68pe/DJ/deuut6o477lBKXdj9w39cR9MXe/bsUUSk3nrrLeeYP//5z8rlcqkTJ07krO254FSLM862bdsUEaljx44ppS6s/hkNZ53aJZFI0Pbt22nVqlXOZ5Zl0apVq2jz5s0T2LKJp7e3l4iIiouLiYho+/btlEwmoa/mzJlDtbW1F1RfrVmzhj75yU9CPxBJ//zhD3+gpUuX0l/91V9RWVkZLV68mH7+85878iNHjlBrayv0TzgcphUrVlwQ/XPppZfShg0baP/+/UREtHPnTtq0aRPdeOONRCT9YzKavti8eTNFIhFaunSpc8yqVavIsizaunVrzts80fT29pLL5aJIJEJE0j+csy6rbWdnJ6XTaSovL4fPy8vLad++fRPUqonHtm26++676bLLLqMFCxYQEVFrayv5fD7n5f6A8vJyam1tnYBW5p7HH3+c3nnnHXrrrbcyZBd6/xw+fJgeeughWrduHX3729+mt956i/7+7/+efD4frV692umDU421C6F/vvWtb1FfXx/NmTOH3G43pdNpuu++++iOO+4gIrrg+8dkNH3R2tpKZWVlIPd4PFRcXHzB9VcsFqNvfvObdPvttzuZbaV/kLNu8SGcmjVr1tDu3btp06ZNE92Us4bm5mb6+te/Ti+++CIFAoGJbs5Zh23btHTpUvrBD35ARESLFy+m3bt3009/+lNavXr1BLdu4vntb39Lv/71r+mxxx6j+fPnU0NDA919991UVVUl/SN8aJLJJH32s58lpRQ99NBDE92cs5azTu1SWlpKbrc7wyOhra2NKioqJqhVE8vatWvp2WefpVdeeYWqq6udzysqKiiRSFA0GoXjL5S+2r59O7W3t9Mll1xCHo+HPB4Pbdy4kX784x+Tx+Oh8vLyC7p/Kisrad68efDZ3LlzqampiYjI6YMLdaz9wz/8A33rW9+iz3/+87Rw4UL6whe+QPfccw+tX7+eiKR/TEbTFxUVFdTe3g7yVCpF3d3dF0x/fbDwOHbsGL344ovOrgeR9A/nrFt8+Hw+WrJkCW3YsMH5zLZt2rBhA9XX109gy3KPUorWrl1LTz31FL388stUV1cH8iVLlpDX64W+amxspKampguir6699lratWsXNTQ0OH9Lly6lO+64wylfyP1z2WWXZbhm79+/n6ZMmUJERHV1dVRRUQH909fXR1u3br0g+mdoaIgsC6dAt9tNtm0TkfSPyWj6or6+nqLRKG3fvt055uWXXybbtmnFihU5b3Ou+WDhceDAAXrppZeopKQE5Bd6/2Qw0Ravp+Lxxx9Xfr9fPfroo2rPnj3qK1/5iopEIqq1tXWim5ZT/vZv/1aFw2H16quvqpaWFudvaGjIOearX/2qqq2tVS+//LJ6++23VX19vaqvr5/AVk8spreLUhd2/2zbtk15PB513333qQMHDqhf//rXKi8vT/3qV79yjrn//vtVJBJRzzzzjHr33XfVzTfffN66knJWr16tJk+e7LjaPvnkk6q0tFR94xvfcI65kPqnv79f7dixQ+3YsUMRkfqP//gPtWPHDsdbYzR9ccMNN6jFixerrVu3qk2bNqmZM2eeN66k2fonkUioT33qU6q6ulo1NDTAfB2Px51znM/9M1bOysWHUkr953/+p6qtrVU+n08tX75cbdmyZaKblHOI6JR/jzzyiHPM8PCw+ru/+ztVVFSk8vLy1Kc//WnV0tIycY2eYPji40Lvnz/+8Y9qwYIFyu/3qzlz5qif/exnILdtW333u99V5eXlyu/3q2uvvVY1NjZOUGtzS19fn/r617+uamtrVSAQUNOmTVPf+c534MfiQuqfV1555ZTzzerVq5VSo+uLrq4udfvtt6uCggIVCoXUl770JdXf3z8BdzP+ZOufI0eOjDhfv/LKK845zuf+GSsupYxwfoIgCIIgCGeYs87mQxAEQRCE8xtZfAiCIAiCkFNk8SEIgiAIQk6RxYcgCIIgCDlFFh+CIAiCIOQUWXwIgiAIgpBTZPEhCIIgCEJOkcWHIAiCIAg5RRYfgiAIgiDkFFl8CIIgCIKQU2TxIQiCIAhCTvn/AdHwQZf263THAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " ship   car   cat   cat\n"
     ]
    }
   ],
   "source": [
    "# 使用了torchvision.utils.make_grid函数，这个函数将多张图像拼接在一起，\n",
    "# 形成一个网格状的大图像。然后用 imshow 函数将这个大图像显示出来。\n",
    "imshow(torchvision.utils.make_grid(images))\n",
    "# 打印出每张图像对应的标签\n",
    "print(' '.join('%5s' % classes[labels[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Define a Convolutional Neural Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "\n",
    "class Net(nn.Module):\n",
    "    '''特征图尺寸的计算公式为：[(原图片尺寸 — 卷积核尺寸) / 步长 ] + 1'''\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        # 第一个卷积层，输入通道数是3（因为CIFAR-10图像是彩色的，有红绿蓝三个通道），输出通道数是6，卷积核的大小是5x5。\n",
    "        # 输入是32*32*3，计算（32-5）/ 1 + 1 = 28，那么通过conv1输出的结果是28*28*6\n",
    "        self.conv1 = nn.Conv2d(3, 6, 5)\n",
    "        # 最大池化层，窗口大小和步长都是2\n",
    "        # 输入是28*28*6，窗口2*2，计算28 / 2 = 14，那么通过max_pool1层输出结果是14*14*6\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        # 第二个卷积层，输入通道数是6（来自上一层的输出），输出通道数是16，卷积核的大小是5x5。\n",
    "        # 输入是14*14*6，计算（14 - 5）/ 1 + 1 = 10，那么通过conv2输出的结果是10*10*16\n",
    "        self.conv2 = nn.Conv2d(6, 16, 5)\n",
    "        # 第一个全连接层，输入节点数是 16 * 5 * 5，输出节点数是 120。\n",
    "        self.fc1 = nn.Linear(16 * 5 * 5, 120)\n",
    "        # 第二个全连接层，输入节点数是 120，输出节点数是 84。\n",
    "        self.fc2 = nn.Linear(120, 84)\n",
    "        # 第三个全连接层，输入节点数是84，输出节点数是10。输出节点数为10是因为CIFAR-10数据集有10个类别。\n",
    "        self.fc3 = nn.Linear(84, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        '''定义了网络的前向传播函数。在PyTorch中，只需要定义前向传播函数，\n",
    "        后向传播函数会通过自动求导机制自动生成。这个函数的输入是一个batch的图像数据，\n",
    "        输出是这个batch在每个类别上的得分。'''\n",
    "        # 第一层卷积，然后通过激活函数ReLU，然后进行最大池化。\n",
    "        # 32x32x3 --> 28x28x6 --> 14x14x6\n",
    "        x = self.pool(F.relu(self.conv1(x)))\n",
    "        # 第二层卷积，然后通过激活函数ReLU，然后进行最大池化。\n",
    "        # 14x14x6 --> 10x10x16 --> 5x5x16\n",
    "        x = self.pool(F.relu(self.conv2(x)))\n",
    "        # 将二维的特征图(featue map)展平为一维，准备输入到全连接层。\n",
    "        x = x.view(-1, 16 * 5 * 5)\n",
    "        # 第一个全连接层，然后通过激活函数ReLU\n",
    "        x = F.relu(self.fc1(x))\n",
    "        # 第二个全连接层，然后通过激活函数ReLU\n",
    "        x = F.relu(self.fc2(x))\n",
    "        # 第三个全连接层，输出层\n",
    "        x = self.fc3(x)\n",
    "        # x是网络的输出，是一个大小为（batch_size, 10）的Tensor，每一行代表一个图像在每个类别上的得分。\n",
    "        return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net = Net()\n",
    "\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "net.to(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Define a Loss function and optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.optim as optim\n",
    "\n",
    "# 定义了损失函数，这里使用的是交叉熵损失。对于分类问题，交叉熵损失是一种常用的损失函数。\n",
    "# 这个函数将模型的输出（即在每个类别上的得分）和真实的标签作为输入，计算出一个标量值，代表了模型的损失。\n",
    "# 模型的训练目标就是最小化这个损失。\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "\n",
    "# 在每一步训练中，优化器都会根据损失函数的梯度来更新模型的参数，从而减小损失。\n",
    "optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Train the network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1,  2000] loss: 2.196\n",
      "[1,  4000] loss: 1.895\n",
      "[1,  6000] loss: 1.679\n",
      "[1,  8000] loss: 1.597\n",
      "[1, 10000] loss: 1.549\n",
      "[1, 12000] loss: 1.495\n",
      "[2,  2000] loss: 1.428\n",
      "[2,  4000] loss: 1.417\n",
      "[2,  6000] loss: 1.377\n",
      "[2,  8000] loss: 1.344\n",
      "[2, 10000] loss: 1.309\n",
      "[2, 12000] loss: 1.302\n",
      "Finished Training\n"
     ]
    }
   ],
   "source": [
    "for epoch in range(2):  # loop over the dataset multiple times\n",
    "    running_loss = 0.0\n",
    "    for i, data in enumerate(trainloader, 0):\n",
    "        # 对训练数据集进行遍历，每一次循环，都会从trainloader中取出一个batch的数据。\n",
    "        # get the inputs; data is a list of [inputs, labels]\n",
    "        #inputs, labels = data\n",
    "        # 获取一批训练数据和对应的标签，并将它们移到之前定义的设备（GPU或CPU）上。\n",
    "        inputs, labels = data[0].to(device), data[1].to(device)\n",
    "\n",
    "        # zero the parameter gradients\n",
    "        # 在进行反向传播之前，需要将模型的所有参数的梯度清零。\n",
    "        # 因为PyTorch的特性是累积梯度，如果不清零，梯度会被累积起来而不是被替换。\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "        # forward + backward + optimize\n",
    "        # 前向传播，将输入数据 inputs 传入模型，得到输出 outputs\n",
    "        outputs = net(inputs)\n",
    "        # 将模型的输出outputs和真实的标签labels作为输入，计算得到损失loss。\n",
    "        loss = criterion(outputs, labels)\n",
    "        # 反向传播，计算损失函数关于模型参数的梯度。\n",
    "        loss.backward()\n",
    "        # 优化器根据反向传播计算得到的梯度来更新模型的参数\n",
    "        optimizer.step()\n",
    "\n",
    "        # print statistics\n",
    "        running_loss += loss.item()\n",
    "        # 每2000个mini-batches打印一次平均损失\n",
    "        if i % 2000 == 1999:  # print every 2000 mini-batches\n",
    "            print('[%d, %5d] loss: %.3f' %\n",
    "                  (epoch + 1, i + 1, running_loss / 2000))\n",
    "            # 重置累积损失\n",
    "            running_loss = 0.0\n",
    "\n",
    "print('Finished Training')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 保存模型\n",
    "\n",
    "PATH = './cifar_net.pth'\n",
    "torch.save(net.state_dict(), PATH)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Test the network on the test data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAACwCAYAAACviAzDAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAATxBJREFUeJztvXl0XdV59/+c4c6jxivJkmwZ29hgM3lCgTchiVsgWSQU3jbJS4sz/JqV1k4DXqtJSJp0NS01v3atZugiZLWLQPprKAl9A2lJQkoMYUhtPGAzecbyrMGSfHV153vO2b8/aO5+nkfWRQL5ysPzWUtrna19dc4+e++z79H+PoOhlFIgCIIgCIJQJ8zZboAgCIIgCBcX8vIhCIIgCEJdkZcPQRAEQRDqirx8CIIgCIJQV+TlQxAEQRCEuiIvH4IgCIIg1BV5+RAEQRAEoa7Iy4cgCIIgCHVFXj4EQRAEQagr8vIhCIIgCEJdOWsvH/fffz/MmzcPgsEgrF69GrZu3Xq2LiUIgiAIwnmEcTZyu/zoRz+CO++8E773ve/B6tWr4Vvf+hY89thjsG/fPmhtba35t57nwcmTJyEWi4FhGDPdNEEQBEEQzgJKKRgfH4eOjg4wzbfZ21BngVWrVql169ZVy67rqo6ODrVx48a3/dtjx44pAJAf+ZEf+ZEf+ZGf8/Dn2LFjb/tdb8MMUy6XYceOHXDPPfdUf2eaJqxZswY2b9484fOlUglKpVK1rP5nI+buu++GQCAw080TBEEQBOEsUCqV4Jvf/CbEYrG3/eyMv3wMDw+D67qQSqXI71OpFOzdu3fC5zdu3Ah/9Vd/NeH3gUBAXj4EQRAE4TxjKiYTs+7tcs8998DY2Fj159ixY7PdJEEQBEEQziIzvvPR3NwMlmXB4OAg+f3g4CC0tbVN+LzscAiCIAjCxcWM73z4/X5Yvnw5bNq0qfo7z/Ng06ZN0NvbO9OXEwRBEAThPGPGdz4AADZs2ABr166FFStWwKpVq+Bb3/oW5HI5+NSnPvWuzz137KekbCiveuz30dsxmKtPuawNWx23Qur8fn/12PU8Uqc8xc7rVo9Ni7ZPVSL6c+CSOp+/WD22gLeVXsP1nOpxxaHt8Tykpxn0PI5LtbYS+ixX4TzUd1yjK5dp/7iuvg7ucwAAE91nmfVdziFFyJf1ZyOXrYXJWL9+PSk7Dj1Rvd2wZ+x6avLyhCr2r4FCnzAnVmoMOgYGKyvAc4KeR03D875Wn+DzPPDAAzXPM/d9aB64dJxHTg1Uj0vFIqmbf8kCUk4m4tVjn0Xvy+/TD6qf17F1wjZ0212nQOqiER+6Br1/G5UttjCcPj1Kytggz+fzkTrb0H9rmPQajlcm5VrejKahK/O5PL2GTdeNYDBYPS6X6TUctG6GgiFSZ7D7/PY//L+TtqezS4dZiDYvInUhy0/K8Vi0ejxeoutoLjNSPTZNtjayp8hGHRSy6Q570EJ9wNbfCYslqnY9d9I6j9Xh9vA+N1nf1XqeDDQnDX7PvD01zolVBr/JFAdFy4Zfty8/sofUPbvl9UmvOVXOysvHxz72MTh16hR8/etfh4GBAbjqqqvgqaeemmCEKgiCIAjCxcdZefkAeOs/V/7fqyAIgiAIwqx7uwiCIAiCcHFx1nY+zhblCRo10mSZvUEAIqRsgtawbJvqZEQ75fKfj16zhDRRx6O6nY20eIvZg9joNIZHbSrAKZEitqPw2DXKhtZnXYvqdGX+WVdf1GDaoIHsSoI+rnvTsmkjHbzC2m7o8yhm56KYeGpZU3vftXjnzTJny8YEj8kEawum93u4LxU3NkJ2HEy/NoA+F/RKZ9/m4+2IhvUcNlncw1JO13llarcQ9NPrR0L6b23WNPw8BWx6zyE/m+uov0ounc8BWz97fvbM4OGybTo+2Obkrc8iDZ+NTwDZn/HHJZenzx6uxnZrAAAKrXcmm0s+Zn+A7U4qJboW4bUgxD0Tp/FceEr3nWM1kLqKj67VrqVtPkwfs/koZKvHys2ROmY+AyWl/7bCbCWKaB4wcxAoV6h9kYnWo0Ke2gHhtYrb72DbOdOkY6e4/Q4abD6WjoPWCfY4Gwb7DkJj29BA+zkQ0rZGJlsnPL5uBPS9uNkozDSy8yEIgiAIQl2Rlw9BEARBEOrKeSe7KI/5biqUF4a56Rku3Y7yKnqbywrR9y689cl3/Lkrkx9trTmKbrN5Ff3H/O/w1pnBtqW566SBXM+UFSR1BVfvEQ6M0K28XJmeN5vV9Zai7YkFkfshc8eMh6lLXSig+9Yz2XYhkgO4XMJ2QaHiTW07nm/bT2cb/2zwbq5P5Al+HryHynawFZdW0P8KpQqd6zbe7nXpWFpGrbZzSWZmmE5/2Ui2M5ls57d0+3wmk0BM2gdB/FnmBlsqaMnGYlJl0KZzvVLSW+4m0GsoR9cp5ubuIjnL76PnNPkYoGeRuzu7SJLN56nUNHLqFCmnmvW2OnfLtfy6fRYT9ficwAqSzc5TQuuqzfq1wuZhLUylP+uytchl649r6H4Oxmg/N83VXpPm2GlSF81nSblc1N8PbpSuo14iWT2OMQkPtxUASIbWcomufzg0QzDI3FWxKz17Jrhsics8I6yD+tnjjyxbN/y2XgtCIeYaDVjuo98dHnA3YWwnMPOys+x8CIIgCIJQV+TlQxAEQRCEuiIvH4IgCIIg1JXzzubDdqkbGFgo5DRzXw1YTI/E/ndMU8NuTtzn0eF2CkgT9fmpptY279LqcSY9TOqGR7R+67OpK5UJzGXW0UNTUGFSt+eI1n1VoInUVSzqslZGOmd2jIZ4PjGo9dJokOnX/WlS7m7T7W2Kcc0ch16nfc6k1Ala72TU0kPPFnWxK5nQH/qayqOVDhN3K8hm6MChQ6Qu1aZDV3ssPHZLI3W3CyIXOu8s3fN0xsuPbDk8h7bdQrq0j7lK+phmbbr6+fL7mPZu6Wv4mM2Sz6Rz3zN0venR9cYpIpdd9qwVUb+Hmc2UxewoiHDPxiCHwsjv2PEyqasUqA1IQ3ylbk+ArmnYPIOnRABmj2ZiWwD2jHrIzk6xv5tgg1cDB5CbJ9D1z7No+0rI3slitk8R5BcbDzObu5e3kXJ5WNuAtC+9lNQZp/TaWDLoWEaZbct4Qbv0BtkXRADZ/ZlN1CXVRK623G26FKY2KHZFn9eqsOtH9NwKjI3Rv+u6jJTzyUT12HOoy7CL5mHQo2MwwQ7RRS7f7szvU8jOhyAIgiAIdUVePgRBEARBqCvy8iEIgiAIQl0572w+uGhu2El9zHRmh6d+R3EBykxb9iPff9fluiazU0DX4SGWV6/5nerxjv/eTOpOIhuQnEO73nGpVnjk+FD1uO/4CVIXaGivHnememhbAzFSLiN91Bdtodcsaj10ZOgkqQs3UFuS41md2rzIbBFSMa15hlkYabdCNWocwbdWhIm3i/NRDxuQ6Vxv6vYiLBaDT+uqrqJ1hSy1N0iPad15cJja74RiWrNuitE5YBo8pg0KuW9MI84Ht8OZ+l/WxI9ssRS7hg9PGGbvZQGP66PrfUDnYQVp3y6zrbHiXPtGtiQsBLbnoP5yqV1JNpOuHkeZnm+y+YHT1Ns+uhakUWyP0Qx9fkIsNHwZdUG5QsfS9iN7IrYWui61l3HQelgu0372I5suxZ59z52aDddboBQAPI6Gou1xHdS3zFjCQDYWRYPOdZ9HbTeMZm0LlR+nY1np2189dgxqo+PR4YMcDvHO+sBf0W0tH2OxedCY8DD6RRZ3xCrqeps2FUpt+p4LA/TZjxl0XTcSzdVjl9uNoefJx9M3sDliIVss25x52zDZ+RAEQRAEoa7Iy4cgCIIgCHXlvJNdSibdZhvL6202l7kVNUTp1l4cudvZbBsUu/hNiITM3MmwW24+T8P7PvPkT6vHg2m6fTmY1X935AT9uyMnj5GyFdQyjGvFSV0krrfZfGEq19hBun0YQFvuQZNuSQ6XdXbG9s5uUlcs0GyRhw5p2WU0TfvZmqPbMK+FtsfHQn0bKFQzc5om8Cyc3A31naL4aWrsJpJwx28ju7hoS9ljW504ky/OcgkAcGokUz3O5Gi/Fkosm2de95gZoO7XuYKev9Ew2+Jn94hFhnejXs2U9BUw9H26Bn3WsHstDnsOcIbQ5x4Ki85Cn9vm5CHCLYNlGyXyDutL5M7vMlff7Lgey6O8rUwuwTJIV5yOJQ6h/sqrr5K6Ky6/nJQ9dC8ll+7VB5E84TH5qJBnsrOt2+MwqdSydfsqDu3zUol+thZYzvbYuqD4/8EovEGZSTQuamtinI1dS4qUQ61zq8eOoi6qgMLPq+Y2UlXw0XG3B0Z0gaWQyKE1V6WoXO3z9H0VmXwfibGwCOO6L0tsjtoh5PbK1gm7qZWUDZ/uH1dRaTCGTmsxGcgxqNuyYeLyzGcZl50PQRAEQRDqirx8CIIgCIJQV+TlQxAEQRCEunLe2XycKlDtabSSrB4/95tfk7rLFlFN7f2XaxekBovZfCA90mSanmlSLcxFbmHMixH6juiw16MFqrepcGP12Ioyd8jGDCmHksnqcblINb4yco+MN9B7jEdpeWhA22pkTjMXLaR5Blnq5aOnaWh4X1xrqUP9R0hddGC8etwWp+cJMe3dYSHwJyOXL9BfsBD3Nhojxeos2zrjMQCAwQx6sA2I6U3+Lm5yx1Jm75BFGj93uw0hV8UiS0Hej2w+hk7TOeCxa1aQ8UZ+nKYOH0Kut8dP9JO6yxbOJ+VL5nVWjy0WSpu0XbH+4CYeJHw3rZrQXzWwkK2Wx12zkS1WYYz2DzB7A2WiUNYhOu/8aN75+ZyoUPsmF5/XZZ8lbsHUbiKX0zYFg4O0bZE4tYVSKL2Dsmlby1n9t0EWJv5UOk3KL7+ubUIiAdrWBfP1uNvMdqWUHyflkK3rvRJ99lzkXuzSpRCgyMakFmhKuB4P4T5hAunPMndeH7IRChw8QJuz4wVSdlYi+x2TrccobYWf2Y4UgY5fFKWbsAL0PF5Et8dQ1G3brejzxpqSpM53YoSUIaufaV+Kfj/AMf1Zm82l4ilqF2QhO0BvEQ29XvTr9pnMzd7vMDsTtN7w6Pwzgex8CIIgCIJQV+TlQxAEQRCEunLeyS52gm4h50f0+1PFTyO9jebpNmS+rCPKxf0sciF25+Lb+BZ1hSuWtbRwivmLDo/rLbhwkrpdNbRod9acR7crm4FlwUTuW2UfbWsxp7dMi1l6nrnM1SuPpJWhMt1ONdCW7tgoc5lj26IFtCVo+Wl/DGa023D/GJWI5jYzCWuK23fpAu3YaJjKSaat939d5gpN1BO2+8882MBEuoth1ngXf5sIqwP9OgptY2MjqQsF9VZnqUj7ORzQdW0tzaROscbn8rpvI366vVsu6rG1WCdnSywzK2q7wWQxKhnxzMJAy5MWJnRXTYJIs5mQWRPJLgEmEUWZ+3UCuQOaY1RKCaD5HOQ7/EziM9EY+dlWPbj6muUMfS5jEf3ZBjYH+o4PkPKhY7q8/+AmUnd6OF09zhbpNfKVN0jZBhSZNEddSZdduqh6/JEP30Tq5rB1ohTU/VPM0b4r53Rb44pF0yxQ+aYWPgtlf2Wum9z11kMRNW32P3L0tG6fc5xGZo4zmWr8pG57OZggdQr094ExMETqIh3MDTaOJAiga1wIRSL2p2l/FJE7tjNM5VA/G1sno8cvMErDK1QKSO4L0e/AdB8N0+APadkl1j6X1FkoqKoy6fNU4m7laG0oezOvu8jOhyAIgiAIdUVePgRBEARBqCvTfvl4/vnn4ZZbboGOjg4wDAOeeOIJUq+Ugq9//evQ3t4OoVAI1qxZAwcOHDjzyQRBEARBuOiYts1HLpeDK6+8Ej796U/DbbfdNqH+7/7u7+A73/kO/OAHP4Cenh742te+BjfeeCPs3r0bgsHgGc44PS69YhUpH9+yr3ocTVA9clXvalIOW9pFtJyj2hy2ITB81P7CVQ2kHGvtqh7vepW+WEWTWrefM5eGQlZIP/YxOw6vRN2uymWtseG2AQBYSIt745VXSF08QD8bjmjtMsJCsZ8cGKweO9zOhWmnjSgEdPo0dUs7ParLff1Ud+5I0bDFNrO1mQw7TjVpl9ljVEykGRsssyYO181sV3h2UWxjoGrEWudh2Vn0d5Kl1GC2CYBsUpIspHKlgq5psbFj7tjY5sOw6PgYyJglEOJhklm2Z+QfPsGFDrseT/CWpf2DrzLxo1M3+jh2+HD1uFKh82M8o59Tt0JtV06coNmeT6O5n2O2UK1N2gYjGmHZRG06XmXkDm376Vpg2trWJsfsd4q4wxRdWo+epK7rfce1a3SuTO13ggkdLtuI0AGiTzBAxK/Hsv/IflJ38qR+vl944Tekbglzv25JahuDQjZN6nIZvTZVllxK6rJjNE1ELQJ+3e+KzXXwmPEcsucxmW1PFmUSz664ktTF7eWknB/X86fCwisYATRGZebOG6JzJIdC1/NUCxVXt8dnUluWAhofHqC8wFyI81nd1gi7fhGdJxCls6AxRr+fXPR9kWVrAaCw8aEKXVMddl+42yvTMeKaItN++bj55pvh5ptvPmOdUgq+9a1vwV/8xV/ARz/6UQAA+Jd/+RdIpVLwxBNPwMc//vF311pBEARBEM57ZtTmo6+vDwYGBmDNmjXV3yUSCVi9ejVs3rz5jH9TKpUgk8mQH0EQBEEQLlxm9OVj4H+iaaZSNLNgKpWq1nE2btwIiUSi+tPV1XXGzwmCIAiCcGEw63E+7rnnHtiwYUO1nMlkar6AhBPUFmDufO3LXmCRu7t7FpByM9LX032HSV0FxflwHRrHYtV7b6Xnnb+ietyzjJ5nx05tg9EQpfYOJ4e07muzMLwBH9PmkMSWZX736VGtwTZG6d9xZc5FthzNLdQmpoS07eHT1FbDsOh7aQyFbbctFg4aad9vHjtO6loaqGa+sJOFDZ6E7//Lv9L2MJsUH9I1ozGqjy7o0fFUVl5BwwuzzOYkNDsPi66whs/0UIfFFsFxHfwB2h4cr8Pvp7YaTQ0oTDxThW0Wy8OPw3D7mCaMUp2nM1SHT4/RsR0fS1ePKzyMPYq50cTCQS9cQO0EfDglOZt43M6kFi/89xb9dwaL/4BsdgoF+hwcHqAxHvAl+Tg3JLRNQyTInj3WVB8Kv26zUNqmrfs9z+I02OgaitnkDIzScPgVFIwmHEvSBoAeSxxqHWBi2PpiUfdJPEZjQ1y7fFn1ODdGUysUWcqGo0f1nHnzzTdJXQGF2T4yQudLIU/HxA7QtRMTiei1wGFjUHH5PNTj7rAYEwaywwmlaOyOTI7216kx3e8GS5tRzqOQ+yzeTTlNz+Mg46iAn665GbSGBH3sK9XUZY/Zn5Xy3M5Ft2+sQNcXZFIGYZv2R6yTfl9auNpkdi54v2FC9gT2EKOH2jsL8dVndOejre2tL9vBwUHy+8HBwWodJxAIQDweJz+CIAiCIFy4zOjLR09PD7S1tcGmTTpiXyaTgZdeegl6e3tn8lKCIAiCIJynTFt2yWazcPDgwWq5r68Pdu3aBY2NjdDd3Q133XUX/M3f/A0sXLiw6mrb0dEBt95664w02Aowd9HBPdXjq5avJHWRBN0CtMa1a57r0C0mG20hHzpG3XCvb+ihjQjrrKCxCN2eC9q6fSEWhjyIt9zZFtycjnZS3o22Pv1+usWeQe5jPV2LSN2ixVRmGB3V26nReJLUnUQhhQ3mIpZsoOGhx9BWvsUkmVBYn7cwTvvjwFGWPRO5jKXOvBn21nnydFu4XKBlH5IgxqmqAGFU5y5ZTOqKim6Vm2jLNMDcKrGU4HJJhskwiUYtaXFXPEBuwjxMsYWlFZYimW90emhb9DDKngwAcGJIj+XoCHXbLhRYltIS2tYv0P4ooYyunV3Udqu7q5OUI368fLD+mUZW210H9L2EQ1SWU0gOLTl0biUaqASLXTnLRSoHnMrq+WOx8YkFqfuz46Ks1T46JhaKT23Y9O8COb0dX65Qw/nRUSp74P7i06Xs6j328RwduzJLO9DVop/Tpgb6QOEsu6OnT5G6piRdU1ZcqcMCHO+nLsxjKJP43uN0bpls3eihU4Zgo74MxejamM1TWcpGupnLpAMbZWM12fPsAS0bFnKbZm3FpUqZzq0Qk8FtJJ/4WFZk7F7rOkwuKerxctgT7Qsx11YUut/P5p0PyXQ+h8lHLA6Aga4TdJmU4jr4g/T67Bc0S8XUn+epMu2Xj+3bt8P73//+avm39hpr166Fhx9+GL74xS9CLpeDz372s5BOp+H666+Hp556akZifAiCIAiCcP4z7ZePG264YYJhHsYwDPjGN74B3/jGN95VwwRBEARBuDCR3C6CIAiCINSVWXe1nS6+IPWGKSJ3t1KJ+tr6mM1FOILd7ai+H0DaYNSmuurD//QgKd/ysfX6Gjkav8Qf0O9zpkn1v575c6rHQ6PUTbCYpRp1W6sO0z6aoXpkqazvef4C6k58yQJqAzK28+XqcW6c6qrYLc1hKa0LzMYimdQuba6idhyJBq2POmV6z5ZJ+/L4SW2bkLoCJuUPbrudlEvMJTQS0uPHXcRCyBbBYIYTPIid5+g547OpNGijEMeK6bwFFgZcefqaJgsFj92Cba4X+1B6e7O2XQkOcVz06FyPxLWtUUMySercMv1s0NJ9lx6hBjPHTxyuHi9gruqWSZcLbAfD7SimE405g+yvlEf7LoxSAoQsOj6dXZeQcgXd5ykWV2gY2cGkUq2kLtBMbVlyaf1Zz6QTKNGgjRoCARrWuoi6Oe/QeRaM0HXLrehn0WLpAfzITdfnp/OlEqTlVddoW41Fcztoe8p6Tel7k/bdm/t2k3LvSu2W29VFz3P0VZ2WosJsCDyXPu+18KN78QfpXPIUdU0OIVdyx6DXGM/oZ89l7rPBBLVVS0WQDRFzF8XrBrdpsNj/5RayxyIu72+DQusqt/lwWbh3pbAtC/2sH1uoMNuwEvuewdU2szFzQc81gz2zhkfvC2VsmGDnNxPIzocgCIIgCHVFXj4EQRAEQagr8vIhCIIgCEJdOe9sPgyWijmPbCWKzC7Ax9LCj48gbdWi9iA+SFeP25NURzyw5wApnzyu45xAntpuHDl+uHp8ddsqUjdnrvbD7xiiDvG5g0dIuTGQrB7Hks2k7s03+3RbO+aQujSzaaggzXHwFPXR95B/uMFCpueZzYdhIq0QKBEUeh08GnvBb7A4BcNnzvHD8SosHgbXYNFx1E/jLYSCetwLRdof+QrV1w8fOqzbyuJ8dPfMrR73HaPj/ORTm0i5Yup5GQzQ0NFh1B6eKjuBIvomEzTGxdVXU6OYlmZtY3BJJx13E4Ult5gmjGMNANCYBYVWqpF3tCf18Rwae8blKcBReGpsgwMwQZauiQ/F7mlppfYGQRQXZniYhu7P5ajtEc4BXqxQHTzRop+9OcyWJZagthvxZm0TMoLi5AAAuEgXZ1OJhH/Ps7gV5QoLHw4otLefPnvBgJ7PPhbHopVFgG5p0OUgiw3RguxT4iwk+MjRo6R85M3D1eO2RrrejA3q8Pe+RpqioWxN/SvERmuIZdD7CrJ1PT2k46KMZvtJ3al+PQ8aYnS9WXrZMlL2Idu+ErMNqyB7FZOlb+DrjYli93ObLmw7wT1BXRKThAfW4IZR+Bos3Qa5Bl0bbXYevBbw8/iwPRFfyFlzTGRP404jXcJUkZ0PQRAEQRDqirx8CIIgCIJQV8472YVvVVloC6q9mW7B4e1uAIBnXtUhyxscunW1sBFvmzPXN5tKEKeGDuvmlOi2bPclOhS7xa4fjuvt3eYUde8bYVkvx5B7LdvthtZWvS1sM2mpyFxdy2j7ucC23x10YoddpFii26KOo99Tm5qpq6Jh6L7zG7SvAsxNzlWTZ73EPPGf/0XKXoW6i5oojHKUuVTH0Nb0vIW0n1uaaHj+pnadAbeR3VcwoiWS9B4qi7225xgpF9B2K/OmBRvtZ8YjVHZZ0K2lnd5V19C2RagME0Fb3HwHt4zG3XHpOOdRFlsAgAoKHx4K0/Ykk3rLf3CAJogcHqYhwkMoS2mqjfZdOEznZS0akKxosW38UknPJ4P9rzQ6kiblTAa5r7LnwkIZQ4+coPcVz1BJJJFIovbQ/ikh136Dze0AzmgaoXMypHh2XDSAbBs9EtJ/61N03nc2UYkxjNxXc5k0qXOQ9GOwLfUeJj3t2atD3C9adCn9MJInTp6kodeDLA0DAC9rsDxhMxdZj0kZ4yiFxKlTVKpNn9Zt2P/qVlK395XNpLxggU43MW/BElLX0IykbyYruCxrNSjdPi5AWCRsO63FrvXctdVjbrAeWYOZ6y86DxdrJmTjruHnTlx/+d+xz+L5zb9XZgLZ+RAEQRAEoa7Iy4cgCIIgCHVFXj4EQRAEQagr553NB09nnIhq3TkZY+5+TLfLKK2XDp+mmlpzTHdFhLmluSbVXQ+fPFw9TjUkSN1cpDEW6Z/B1h17qscn+qmtSCxK3f18KLzwGwepWxx+Z/TY+2OJaXNZlJI72Uj1WAcZDvQPDpG6SIzel41CAYfDVM/2+5GeXaHuvG6O3meqldoxTMa2na+TcshH3VdLJe1C6/fTPlh97crq8ZET1DZjhHrtwdLLdXhqP3ODzSO7Fx+z37nmGuoGW0Spzv0++lgtnK/tgC5fQvX0juZk9TgepvPXK1K7m2MDOi360Gnar/3Dui7HQvWn02lSLld0W33MzdMf0H3gOsw1kbmvhpN6LJfC5aQukZjaOANQ+4x8gd6zhYwVLBb+3nXpuNu2tufxFK3zB3R7mpupC3E0Svs9iOZBIsBC7qN5yMPfKxR63HHow5+IU1sjE4XS91x6zzZyr/VK1BYsEWDXdPRYuszWp4xSrxfYXAqz5/vIgH5ud79J7a1KJb2GVIp0DihmuzFVLLaO86zniy9dXD1esIS6lefHtQ3IGy+/TOp2bt9Cyi88r2219uyma8qiJVdVjxdeSu1Bkg1JUsbu0NaEe8Zj4tWoY8+TR+3sPDZnSJ2rz+Mygy+PnXeqTrEGt/kw6H2ZyCXfmeAW/O6RnQ9BEARBEOqKvHwIgiAIglBXzjvZhWfPbGvVkQtt9i7lMdfS9k69/b0dSScAAGlDR+5TFt22TjTT7bFEXMsyviDdXp6HZJdogrr+PvT9/696nGdtyxSoG2MeRUtku/jQhrLIFkepC2guwNuqpaa9+2ik1sFBvVWfYRlvk0l60XhEbxtbzP3Ph7JnWnnqitcSYdvPQT1+POYj5tQxFvG1kcpSnZ3atfOyKxbS9qCt6Td2UVe8FNvejaKMokPDVJOJxPXWdFOc/t1HbnovKZsopGciQbe0m5v0PBgdpbJU3xE9JmNpGo01M0YjeI4j9+t0js7R0YzOTuswt2Sfj8qI/oAumyxbZSKu+y7JsuM2MMksgOQ3f4hKcVkWIbcWTSj6KI9sGw3ptnoui2Bs0jFpRdFRDZvdM4p06WdSSpBlWLVs3SdcWjFwqk9WhyPL5nP0eeJZSrFbrmLZjPNjeo6cOEyf2VEWljIZ0udJNSVJXTCox4S7Siqbyoh2WLunnzpOo/l2teu1MVam95EpTd0FE7uWmibd4lcsezCOKGqx6KfJpq7q8fU3UBfvBQt6SPnF535dPe7ro2tTbqdegzPMTXnZFVeScleXvqbN3MFdR68hLnefRdK/4s6sTPYwkMTIphYYJnb1Zd9zPDIp+uyEiKu4fRNcbfl5J5d6ZgLZ+RAEQRAEoa7Iy4cgCIIgCHVFXj4EQRAEQagr553NB3HrBIB4g9aLHZfeToDpmot6dCjt7Tuofp3x6XDDnkG19tQcqjnu3qND+L7nfZ8idZv/W7t65XIsw2x5uHo8NEBdQPl7YLaiyzZQDb/B1PYhc0L0GmOnqEbsWNpWItVK7SZcFDa5wDT6YiFPyjnkDul4VM+uFHWWyVYf1eU7otQWoOTo+lo2Hyf2v0HKGeaqeMvv/kn1+KabPkjqfvWMdhVsTdJxbg2zDLgozHXQoHptKqF18FiCZhMNsrDkDtJzuU2Bg0IaD+yjuvPRIR3qu1yhGqwdpG2NxbSrdGuQ9mulPLmbno+5jlvIzsNiNh+xmO6veJz2nWVR3Teb03NkcHCY1BWLdP7UIozsDSrMJTSEwtEn41Tf95grsO3XbrChKG07diM0mWbvKeZiiJ9F9u8Z9uBVzK3SQXPbcen9Z0Zo/+AW+JjNR3ZM22L1n6T2F6lGOg+TER2aPs/sMTxku+KwpR67BQMAzOnUNg2XLpxP6q66TJf3H6Lr1s7X9sBUMZCdh2nQ9pg2tYHzIdd+l7mAGqjfTeaCv3ARdYH3UFqI/v7/S+pOD+u+PVAaI3WDJ/aR8iULtevvksvpNVpT2nXbZt85TkW3r+LwVBPUPg/PUaNWFllmP2TUcK5VvI6MAT8tMx5BhicTsuzOALLzIQiCIAhCXZGXD0EQBEEQ6oq8fAiCIAiCUFfOO5uPSJTq4A3NWvN0mI5YNKkeGIxqvTSZpLEYjh7TIXuvX0lDRRezVGMLx3Qo8v4Tx0ndwf37dXtY2GTs2p7LUI0x1kRDPo+Nac04EaU2BJcuWlY93vbKXlL38p4+Ur7+/R+qHvtY6vlDB7V9SDpDNWoetr1Y0HYec1NUTw+h9OGNTJNWNtU5nfLUwvQW8zSOxbIrl5HyBz74gepxU5LGU7lutY7BYTI9PcZSrcfRfLL8LJS2X8eG4LEYPKBjO3Zax2aIM93XAz3w8y9dSupaOxdVj0dPU/udGIuzUUE6vcHCh/vQ5OKpuotFas+TRTEoFAvxnEVp2I/107gn3A6oktfndV16nnCE9kEtcsjeKBbidib6mR46RWOkZMbSpOx5uk8WsLTwyUa9Tlg+bkNAy9hGp1ymtgh5FNOmWKL94ZT1+BkutcFRJXoenMIhmaRpD0J+HVfDNui8SzIbqkRMl8vsGnnUH+USbY9p0OeyAdk0hQN0bh1HMXcs9vhefimNsXMKhfnnmMiGgMdrsth9+lG1x2KC4MAWPDZFmdk+dXbNqx7PmzeP1G0b1PPbYfZDp4bStIzsQ/bseZXU9fRoe8FLLqH9kUrp0PAxFtIeDGpHUSyjeCFsnfQheyYeu4OHV8fVyuDh3sknaXNYLA9csqYctH3qyM6HIAiCIAh1ZVovHxs3boSVK1dCLBaD1tZWuPXWW2HfPmoVXCwWYd26ddDU1ATRaBRuv/12GBwcnOSMgiAIgiBcbExLdnnuuedg3bp1sHLlSnAcB77yla/A7/7u78Lu3bshEnlr+/ruu++Gn/3sZ/DYY49BIpGA9evXw2233Qa/+c1vZqTBnkO3OhON2gUzV6Bbv3nmTobdCru7Oknd/jdQmOs8C/Ec6Sblrkv08ZH9NAz4CeQa19u7irYHbWnHOmimxsYOGhb46KiWUwol2h5/RG/Txlu6SN3VMXpfp9BW9eEju0hdLq+lg/QYdZ9tbWkh5YTS9zU3SmWO1rjeFvUZVC4pV6hDbQRtt1KHZsr8xVeR8sfv/H9IOe/qLct9B+nLrYe2M4PMRbfCthZH02jOeHRuuSicN1P0wAO6xT2e0XdjDdKt35NDWqYrse1vD2UJjTA34EMHqKTXd1RnN+bhwxub9Zjw7fexMSrxjQxrt0/F5BIThbk2WMjrSIhmf00iV+Agy/pbyNZypKYEUPj3kWGaXfnN07qtPGtrsoG6jre3p6rHZZYhtFLW0o7HXBwzTOIrIHnJdeg1LSS/+X30fzcspQQjtK9CLEdCEa0FHnPZjURRKgMmT/hZRlW8pnGX6iJy7TSsyd1VAQAqFb0WHB+hGZPzOT1/uCtpWztdb2phIQnA4nIAc0MFA43fhDDg+G+5vyj9LM6WG4tRSZi4s/IMxTz0udLtGz9N5+jOYZRl95VtpK6xSc/Rtja6Vre1z2NtRekcmAzfktIhJQzm8s7ns4OkVIe55ZLw6jyEu0fns0Lyo/JqyTfvjGm9fDz11FOk/PDDD0Nrayvs2LED3vve98LY2Bg8+OCD8Mgjj8AHPvCWJv/QQw/BkiVLYMuWLXDttdfOXMsFQRAEQTgveVc2H7/9j6qx8a3/xHfs2AGVSgXWrFlT/czixYuhu7sbNm/efMZzlEolyGQy5EcQBEEQhAuXd/zy4Xke3HXXXXDdddfB0qVvWfAPDAyA3++fkA0zlUrBwMDAGc7ylh1JIpGo/uDsgYIgCIIgXHi8Y1fbdevWweuvvw4vvvjiu2rAPffcAxs2bKiWM5lMzReQ8RHq/hdCrpMlFprZ8Ojt4ZTFzY3UbmG/eah6PDRKNeARi+pdiajW3xYvpe5Thw5rXb5CpTjizrpwIXXJWthzCSkf6dc66xtvvEbbM4xSmQeoTUMDCyt9/A1tO9I/THeVDOSKbAXp37V30RDLc5E+2B2jenbQ1HpoqchTSlMdmocYnoz/fcf/IeWGNqotv/K6tofg7nVlpE+6zI1SMV0Tu5AZzPXMxZonqzMnvLbr+opD+2B4RNuk4BDcAADYrCIZT5I67uY5OoLmJdPwh4e1TUOJ2dk4LHS+W9bPieWnz0g4qOdEgIVetxx6zXIR9zud7Dgs+tuRRm7KJ0/QcOIR5Ma9+DLqbt3YTMOth8N6XhYL9Bk+fVqnJKhUmEuqoutGGIXOT8SpjUMkoMshZmNhI7sBl7naOg69RgUtDkWTPhM4XDZPPe8yOzYckd+2aGgB5elxL5boHBg5RcO9D6Pw7+Pj1BrrdDpdPeZ2SYEYXUdrYShs80HruEuogewYDDV52G9uq4FdUgEACll9LwMD9Lvj5EldHgvTv/Ox5wu75EeCdG6Hbf233OX8RL9epw4cPkTqCoVNpOy4+prNLR2kbtmyy6rHCxfQ78eWFvocxBParTwQYqEPALWd2XE47PsKDOSqfRZcbd/Ry8f69evhySefhOeffx46O/WXQltbG5TLZUin02T3Y3BwENra2s5wJoBAIACBwNRjAgiCIAiCcH4zLdlFKQXr16+Hxx9/HJ555hno6aEeGsuXLwefzwebNuk3un379sHRo0eht7d3ZlosCIIgCMJ5zbR2PtatWwePPPII/PSnP4VYLFa140gkEhAKhSCRSMBnPvMZ2LBhAzQ2NkI8HofPf/7z0NvbO2OeLocO0q2r7oVLqsdBk25temW6/Wyj7bIg2zqLxbR8EY3TrarFi2m0xF/918+rx/kxassSbtLufgePU5esrk7tsttz6TWkLsC2v+d368+mR6nr2+492i3YU3TL9vhp2gcZ5H5cdOkOUyatZaBW5gZ2ZIS6nTZ2JavHI3ynykMuu0xWUTaVaEqe3vKutd+1c9d2Un71tV2kbIA+r2Wx7W8kxVk23/7nGV71Vqftp+/ieI74fPTv/KwPTBQN1VL0s3G/drczmUxWsfD4sGiwbLfZH9YSRCXPpAOUQbnM3EONCst4izSjMtvGd1Gm2tw4PU+YzdGWhL4Xm2X5xYrE2zndNrboZ6aBSSk2Hh/2zI5nqXt4Nqv7IBBgch9yJfWYG25HirqVB5D0ZLHItsrTY5Qr0jsrInfrNJJ5AABGRmnkzwKShZYsoeuLD+0a881ui6Uixe60pRyVS46jzNk88mi5TNeJfE63ZyxNXbP9KMos7/NNzzxDyu9dfTVMCoqq6rEMqsph2WCRRMOUUjCQvMRdQC3mQvzKyzuqx9nTtA+aUHTYY/20Ls6yWPvROuYx6TQeRZFbWfRcv62v4QtQycoymbx/Ol09PtxHs3qnT+uxfHk7W4tYZOYuJJl3tNMwEe0dep3vSNG6SJS6rhsh3fGGOfPqxLRePh544AEAALjhhhvI7x966CH45Cc/CQAA3/zmN8E0Tbj99tuhVCrBjTfeCN/97ndnpLGCIAiCIJz/TOvlgwdeORPBYBDuv/9+uP/++99xowRBEARBuHCR3C6CIAiCINSV8y6r7a6D1I6ie6kOYe4B1dAM7taJdMYMcydLp7WrWVPjVaTuQze9n5SvunJx9fjHP3mcXtPQml8iQTW0OR3aMyjK3Coth7a9sU0PTXsP1ajHQlrje3nXLlLXn2Vhgn3aFTjRTt3imhfoOm4b4bIw5PuU1isPDlCfLD/ymyuwDKo5NgSOp/vnZirvE1547mlSzmfS9Jo+raWGwtRNGE9rS9EpzrNgmj5s80HvORjQOi8PH+4P0uyidkT3bdBP3a8DptZoba5fB5GrL8vsWSlRXb6IXGaxDQMAgIddFdl5bOYmTNIrM9uIZESXExHad9EQdUcM+PQ1fQadowYLhV6LCtpR5f1sozDyLgsVzTOh2sg1mJlGQBDZcRRytO8KY3QtKKAitwMyUUh1xWx09u3ZXT0+cvgwqeMZrhVyJe1op56AjQk9fwp5anvFy2lkJzCCXJYBAArI5s1lbc3z86DgjiabL2Fbz4P+k9QVmsdvqmXzUUG2SNw93nDoXMNZd3lgbwW6jrvsZrN0LIsFfc1LFy0hdddctaJ6vOPV10ndlm1bSTmd1euzy9ymW9u1W+z1119P6mw0nw8foak4tmyhgTeXXqazqccTdA0ZRP3Mc6XxtaAtpUOz9/TMI3U4fEBunNr28HACPluv+UU2XjOB7HwIgiAIglBX5OVDEARBEIS6Ii8fgiAIgiDUlfPO5mP/GI0bMexqvV/5qL2BWWaaFrI34GGLO9q1AcL/eg+NwRH0URuHnrlzqscf/t8fJ3X//vjPdNsG6PX7x7TeViweJHV+oJrsaEGXDx5heXGQ/qZaFpOqhhS1RfCQjmcYVN/3kN2CZ1A9v8LiP4yhFPZBH/1s0NbCa86gWnKFxcdQHtYOJ9cRUy3Uz76/QP3wXTddPY7/T2LD32Kj+8wM0xgp4xlqW1NxcfwHZqdQK420Se/LF9LzR/lo2x1DP2YmM/oI+/UYREJ07NzK5DZLEKDnMZC9SpDF4wgxO4rGmNZyu1g4/s52HZqZhe6AUpHq6abSz5vNxPdkXD+neWqKMIH9+/dUjy+//DJSF0K2Gnw4TBYFw0OpxAeHqG1YLqOfxVKBxmlwmW0Yto+Yv2AeqWtp1f3jsgb5kH1KksWJwLFDAGh0fB76fO++fdXjbI7G1eCfxekKPOaNmEN2bXl2z/k8fQ7KyL4o4KPz5+igfvbSKNQ6AIDrvb0H5G/B3pLcvoAXcbp7FuUfPGQPwgOhhML0GfpfN3wQfZSeyEbxSxZdtYrULV2+kpRxuBc+75qbtL3X/Pk0TYaNxn3ewitIXUc3je8SCulnJsFsPnDfjY7SBwrbcQAAtLZoG6JYjJ7HQvY7Jgug4np0/augMfCMqY/zVJGdD0EQBEEQ6oq8fAiCIAiCUFfOO9llX5q+L/30RZ3x9aq5zaSuzU/D2YbRdmI7S3TX3qy3SS+ZTzOoAst62X9Kb3t9/9Gfkbodu7S7Hc+yS3Z3Fb0PxVzx3IBuj8u2+G0UWtwxqHzkmCzjLB5h5j5bLCO3QeabaDPXWwttMasiCwOOnOF8PGusQcvlytSyI6oKlW8SEbptPY5ceisu3ZpevGSpPk8HdS8eYtk8h1A2z2yaymvYHZG7KiqXbn9HbL29ufjKBaTuJHLlPJWhMlChrNteKNJ7ttj2bgCFjY/4uIusHveWhiSpa++gc33BHB3OvDVA508WhWkfZSHBLeZ2Go5oV/Ioy3Tc1KTrTvZRF0NOBck5xWya1JnouZiQWdiiy5eLwqYfOLCf1I2P6fP6mazgD9C5jkO6eyzVp4kzFjNpsgnJf9zVN1+gc7SAyseOHSd1+G/Z4wOKpVPOl/U85JJIblhLTT52zw4Lue+gbKw5Fl7dQaHgedbWCXpJDQpI+rEyVMKzFcuYjNZch2VMdtAY8PZ4TArDSpTDnmEDpxnw6Hk6umneMvCQS7xHB9dEa3nfURpWv1DW7THY2MUS9Bq47afHaFttJJdE4vNo29i6Pjqm+/nkIG0PDmsfMOmayhICgxHV1yyepuvdTCA7H4IgCIIg1BV5+RAEQRAEoa7Iy4cgCIIgCHXlvLP5yDKd6lcva213/5uHSN3Ny6nb3iUdWpfvO3SA1L13pbYTCDI9fbxM9cgfP7WtevzybhpuOI9TQzO7CRyamaeUxuGEAagNhsv0yBKyq6gwzdNgYa5LKIU8TwxoI7dPi/mzhcNMD0S6K/PsAhe5knK3L4e5i/pjSVSi7pCYkZNUB3crVHMsIK05f+woqWu09D23BKndj69E7SpCpm5vwWJpvhVue22tO1/QtiPvXXk5qbt8ybLq8dGj1P5hJK1tQEosnDqwOWIj9/AQS/XejNxpkxF6zy5r+8Cw7q99w/2kzkCugfFWai8TilO33DBy2W1spp+NMlfBWoTQPCwz2wjsxm0w93iTzVkT2TXE41F6HhRGPxqh7pgWc0UOB/Vzy20jDuzdWz0eG6V6+hhKae8q2uc+P207DgUfYGK7gcY2X6QuskPMzTKPXG8t1j8NiWT1uMzSHuQL1ObCqej2ehPsOrARCrUvMLhRSg2ef/7Z6vGY8yqpi9jMzRw9pxVmx4Hd412Xjg9f4yrIDoivo9jttFiidS6z5zGQTYrPZq7rSW1rGI0mWVvRms/diSf0pS6bzD4E97PJvgNtm5ZN9Fk+Prh7DLaOGwb7LgmjaxaZ/Redau8I2fkQBEEQBKGuyMuHIAiCIAh15byTXZqaW0h59LTeR+pHGR4BAP77lb2k7FbmohLdqmpp0+61hkW31bZupxkPf/aMzkZY8uh2IaAtOb51RtrCttgV25PD0Rr5ViLOOOuz6RAafD/M0vdpszoLuSrGYnSb2mJttxTavmRuwh6Sdrgm095Gt99jcVTOTy67tLXTqKXHjzIZpoSjHFJpp2+/jhA55qfjw0ckhyKu5hy6hesR1zwuk9Et03JJb2O//OJ/kbobIrpvl7J+LSS0lMHdOnlW5iJyqxxjWWOxy/CRvTTr5XAhQ8pFn257qJX2c0NbsnociDN5gmW1DaMonoEwlXoMa+pLC4427Dp0/uAs0bx/SiUqHWBX2xB7LkwkpRZyNLpnaZRKp0fzWvrx2BgY6Fn0MXkWu6f7gkwiYt1RLuvzjp+m0kqxmEXHVCbkjupBNJ8qBbqmVEC3ocAinPIydvM0mJ+wg8ZHuXT++n1Tc50HAAiiTNQVi80tj3ZQAIUa8AzmUo3aarK2cndsz9P9PFGCQFKTYll2WU8rtOYaLLwBVnNMoGNgW/r6pRJ9ZrnrLb6k4zD5CMnXXCLn0bpryTeYMssArJhEXsTJry0q93V0zIV3i+x8CIIgCIJQV+TlQxAEQRCEuiIvH4IgCIIg1JXzzuaD2y34UMhpp0g16b5BqnWXcjp75nuvWUTqQsn26vFYkerOz720nZQLyAWzwuwEAihUMw/1i8N1cyymaxKTAuaiFUB6usHFZFY2AlpbxVkTAWjI3grT+8aZLo6zV5aYLp9o0K5mbSgrKgBANEjbU0CZNmu9+nYv6iblTI6OZe44DpPOwsYjV8FR1lY/6+cyGkvuHlkrdLShJq878OpWUj42rnXgFpNq3diex2X6bNakbR9QWqc/yFyGj6OMvPkwvcdYdwcpp3q0XhtM0uyrZP4wbTkapXZBYeR6a/qonZSahgtmJq3HMj+eJnVDJ/UzXSxSzdxlWYgrlTI6Zq7raP6aLAOvj2Wtpi7ozEUWuezyEOoV5PZZyFHtv1Siz9M4CoGtaFMhEtdrCLe9UhU6J0pZPQ8ch15zDNkYcBsP7naKbRw8NXk2Z9umdi6G50zyyYngrNHZHE0zELb4/EFtZQsFzuRbZmkYHIeFATf1ZxWz68DzxXNY+HnmausieyNuO4KzCXMTC6X0PZeY2/SE0PA46y+zAVTEXd5ldcwtGH15cIscfA2rzPuDjmW+QT/f7V3Uzb4DxOZDEARBEITzDHn5EARBEAShrsjLhyAIgiAIdeW8s/ngvv44Nb1n0XDmZaB67WBW628v76O+/R/Kay1sXFH/5xOnaTmItG8nT69RRDprOMxsLHz2GT8HcIbQ0QYO50uHSSFdXrH3Rx9LD55FYZPLDtWdsQ0IjyXC7TpyRa2PRpPUrqOhRadsLzPdee9eGmvFh7Tm5TVkw3gDjT/Rkmol5X5k8zFB10THJWbHUWGmGjj0uDuN9OATPokaUWH6em5YhyY2A0lSZ6Hw2CeZlrsL6Bw5aOs7y0Wp9h7p0insWzrmkLqmlhQpB1B48TK7E4X0/oDN4sLwMrKHsHhcjWnEXx44rFMkKGYnhXVxHn/CDjD7AwvHYqCf9SOblDCL/cI/i221HBbnI5vVOnm5ROs8ZKhgslDVnkufC39Ax0VJzaE2OdmsTmmfOU1tI5wyiw+E2sdjU+TL2B6E2cBwmyUcQZ2dx4f63QJux0bXxlocO6bjJR3op/cRYSHmbWyLNeEJ1+PuuGwMPGrH4A+Yk9Zh2xEWpX1CGHkcW8MwWMwfPC/5HEX2edwGkKdT8NzJY62YyFbNMOi856k68DNcY5ihArTv3Eb6XMxZptOTJGgYn1rmcFNGdj4EQRAEQagr03r5eOCBB+CKK66AeDwO8Xgcent74Re/+EW1vlgswrp166CpqQmi0SjcfvvtMDg4WOOMgiAIgiBcbExLduns7IT77rsPFi5cCEop+MEPfgAf/ehHYefOnXD55ZfD3XffDT/72c/gscceg0QiAevXr4fbbrsNfvOb38xci3lqQLTFZFlsO0rRrV/X1PV9Q3S78Ps//nn1+AM3rCB1fSdpRr8czlTIZQ+UFdRiW4lhtHXnD1F5pDBOJRHs9qSYBOJD7qt8K5y7S+Gtcb49V8BhpFkddzFMIhmkKdVO6k6N6Oye6eEBUpc+QrMHL5jfA1MhxLLRBljmUZ9f96XL3A/xnTgG3x9kboRqkuO3YYIzItqmzbK+3Iu2vxN+KsXtLeqX8zeYLDbCwps3dem+a++h0koShaMPRKhLrOnRLdwKfmZYRkwLyRP2hGyr9DxEEjH4NvHU/6+xPC1TeSw8Pw5vPuH6zK3cVHhrml6jhMLROxXaz1guAZjoAonB7uk+P52TFnJDtXlKBPYMBwP6PIEQPc/oiG5rbpyuUz4mz1qon8tMynXw9nsNd0wAGoabu5EH0RqTzaRJXT43BlPFVCj8PJcDXLp2Y1loQuZcC4VXV5OvdwA0hAH3pMfzRbGQ6XwCKRpDnYDlFB4KwkFtr7C2euz7SqFsxlwuwVnO+Y0YE8ZWX1PZtLEOyqwe72gjdZ3LaPgJ29DzMr3/NdqgTirlvhOm9fJxyy23kPK9994LDzzwAGzZsgU6OzvhwQcfhEceeQQ+8IEPAADAQw89BEuWLIEtW7bAtdde+64bKwiCIAjC+c87tvlwXRceffRRyOVy0NvbCzt27IBKpQJr1qypfmbx4sXQ3d0NmzdvnvQ8pVIJMpkM+REEQRAE4cJl2i8fr732GkSjUQgEAvC5z30OHn/8cbjssstgYGAA/H4/JJNJ8vlUKgUDAwNnPhkAbNy4ERKJRPWnq6tr2jchCIIgCML5w7RdbS+99FLYtWsXjI2Nwb//+7/D2rVr4bnnnnvHDbjnnntgw4YN1XImk6n5AtLEXm6KRa2J5lhKab9F9XUH6a48HPRzW1+tHvedpG646Rz1wxrNao2aeZZCBOntDnOtCgQm19ODIarjWUjbtX30szjcsMPsC4wJblfIlbRC76OMwguHgtQGpbmpiZQbm7WdR1nRd9aSX0+jQoC21WNpx3MsxPBkVJgLXa5Ate9YUre3mGNht1G/u0wvdrldB/qFMbnUPwHF7AQUcqnLmbTtL5S1Ln4kT+tGwrp9dorO+/bOFlLuadHlpgQdHxPNuxzTgIvM7sVGGn6Q2dIEw9rWxvbTOREMURuUAJozPL38dPCQnyN3AVVIJ1fMdkUxv2lig8KugdOXu9wugD1f+Dm1uAs8+ls+lbBdgFuhYb5d5n5d9um+KxSoDQq28/CYi6zhZ679KGXDhL5DU5+3ldt84Hqbh3Qv6+fr9Ah1IKiUp/Y8AwA4KLy6y/6uzFIJkFDxHrPtQUWP2T+YrA/KaEw8bnOB7Is8j96zn30/4GWEnwfbInHzFA+HMGf2TNy2htiLsPExkJ0LcHdidtEK+g6oROjcbrz0kurxnHl0vSky55A39+q0IqFKltRBJ7xrpv3y4ff7YcGCBQAAsHz5cti2bRt8+9vfho997GNQLpchnU6T3Y/BwUFoa2ub5GxvPej4YRcEQRAE4cLmXcf58DwPSqUSLF++HHw+H2zatKlat2/fPjh69Cj09va+28sIgiAIgnCBMK2dj3vuuQduvvlm6O7uhvHxcXjkkUfg17/+Nfzyl7+ERCIBn/nMZ2DDhg3Q2NgI8XgcPv/5z0Nvb694ugiCIAiCUGVaLx9DQ0Nw5513Qn9/PyQSCbjiiivgl7/8JfzO7/wOAAB885vfBNM04fbbb4dSqQQ33ngjfPe7353RBheZzQCKngslFiPXZ1G9y0GSmmK6phnSmvlhFtfDZLE0HKQ1O8x/v1jUWm+OpaXHvvRcaor4qWYeQnFATKaH4pgXoTCN6VAuUz3y1KiOweGxcLo28vluiNO4Gm2NSVpu03Ek0szGIpPWIaCzY2lSl2ykYdKHTw2jEg3Tjqm49BqWn+qjDS26vZUoG2cU94OFAIEKs8NRyOaDdTMJMz1BI+eBJHCMB5vF1Qjp9pUStD8uSWp/+YZGmt4+GqePZzSs52EgSOuKKO1AmafcZvYYFgrzPyEgBir7mF0SjynjQ+fh8RV4XIlaFFHIcJunEkDtmRDCnaV3N5Hdjcmeb2y7MSH0Oytj+xAe7h2HKXdZOvkKGgOLrVOVLLVZclF7IiVqv4PtPEw2PqUCSxnP4x6RqsnreLh1G80RPpajg0PV40qJrml8+tQEndbysTgj7Pn2obUJXLZBj4xZLJZCgzdHIUMug9lpBZH9TEOcPpcm8Ngvk4+7hcL6B5jNm+MgmzJ2Th5u3UX2KeMZOl+waYvH5v2YQc9jN+t7mbuIxu5oaNBr7om9B0nd8MFD9DzoPoO+6Qz01JjWy8eDDz5Ysz4YDML9998P999//7tqlCAIgiAIFy6S20UQBEEQhLpy3mW15duOAbTlFWZ341Xo1ieOoOuxANkeCkXssa08p8xc2Fx9zYmugbrMt9XwVvDpUZqtcpS1NR7TskKCZXiNozDtQaDukK5H5QobbTtaAXpfpaL+bJBJBTbzO3XyY+iYXiObHqkeexXqexxkmUeLU8x2yrdlk01UXopGkOtkiY4Bll0cl4de52GlUUhu9i6Ot7xN7nLJwhbbaNs4zOSJGBrLVDRJ6qIB7Q4eYaHX/azvyqiY9dPrF/C2MHO9C7JtWr+FQ4TTbWIsSRjc5ZK7MSI3Qr+fuf/5pp7VFmdi5v3sQ23gUopi94lHdmJUfRy6mm6bgzu5qzbPou0gd/UyyzBbQFKLW8iTOoe52kbQeUMJKj86qF8rRXoNLsNguDQI2OWch+tmslgErSm5DF2bMjikOjuPaU79K8TCuneZrb8sg7MC3QcW0Plro/LEjMTMDRZNBJ6N1nP0NfI2DW7Js4wDkjJx1lgAAA9lDi9WuAyEs+HyEO7sEqh5LrA0u6jt3FU83soygC/SaRhM9j23b9tLuq1Dw6TOYnPdRnOiloT3TpGdD0EQBEEQ6oq8fAiCIAiCUFfk5UMQBEEQhLpiKC7kzjKZTAYSiQR8+ctflsingiAIgnCeUCqV4L777oOxsTGIx+M1Pys7H4IgCIIg1BV5+RAEQRAEoa7Iy4cgCIIgCHVFXj4EQRAEQagr8vIhCIIgCEJdOecinP7W+aZUKr3NJwVBEARBOFf47ff2VJxozzlX2+PHj0NXV9dsN0MQBEEQhHfAsWPHoLOzs+ZnzrmXD8/z4OTJk6CUgu7ubjh27Njb+gtfjGQyGejq6pL+mQTpn9pI/9RG+qc20j+TczH3jVIKxsfHoaOjY0IuJs45J7uYpgmdnZ2QybyV6Ccej190AzgdpH9qI/1TG+mf2kj/1Eb6Z3Iu1r5JJBJT+pwYnAqCIAiCUFfk5UMQBEEQhLpyzr58BAIB+Mu//EvJ7zIJ0j+1kf6pjfRPbaR/aiP9MznSN1PjnDM4FQRBEAThwuac3fkQBEEQBOHCRF4+BEEQBEGoK/LyIQiCIAhCXZGXD0EQBEEQ6oq8fAiCIAiCUFfO2ZeP+++/H+bNmwfBYBBWr14NW7dune0m1Z2NGzfCypUrIRaLQWtrK9x6662wb98+8plisQjr1q2DpqYmiEajcPvtt8Pg4OAstXh2ue+++8AwDLjrrruqv7vY++fEiRPwh3/4h9DU1AShUAiWLVsG27dvr9YrpeDrX/86tLe3QygUgjVr1sCBAwdmscX1w3Vd+NrXvgY9PT0QCoXgkksugb/+678mSbEupv55/vnn4ZZbboGOjg4wDAOeeOIJUj+VvhgdHYU77rgD4vE4JJNJ+MxnPgPZbLaOd3H2qNU/lUoFvvSlL8GyZcsgEolAR0cH3HnnnXDy5Elyjgu5f6aNOgd59NFHld/vV9///vfVG2+8of74j/9YJZNJNTg4ONtNqys33nijeuihh9Trr7+udu3apT70oQ+p7u5ulc1mq5/53Oc+p7q6utSmTZvU9u3b1bXXXqve8573zGKrZ4etW7eqefPmqSuuuEJ94QtfqP7+Yu6f0dFRNXfuXPXJT35SvfTSS+rQoUPql7/8pTp48GD1M/fdd59KJBLqiSeeUK+88or6yEc+onp6elShUJjFlteHe++9VzU1Naknn3xS9fX1qccee0xFo1H17W9/u/qZi6l/fv7zn6uvfvWr6ic/+YkCAPX444+T+qn0xU033aSuvPJKtWXLFvXCCy+oBQsWqE984hN1vpOzQ63+SafTas2aNepHP/qR2rt3r9q8ebNatWqVWr58OTnHhdw/0+WcfPlYtWqVWrduXbXsuq7q6OhQGzdunMVWzT5DQ0MKANRzzz2nlHprwvt8PvXYY49VP7Nnzx4FAGrz5s2z1cy6Mz4+rhYuXKiefvpp9b73va/68nGx98+XvvQldf31109a73meamtrU3//939f/V06nVaBQED927/9Wz2aOKt8+MMfVp/+9KfJ72677TZ1xx13KKUu7v7hX65T6Yvdu3crAFDbtm2rfuYXv/iFMgxDnThxom5trwdnejnjbN26VQGAOnLkiFLq4uqfqXDOyS7lchl27NgBa9asqf7ONE1Ys2YNbN68eRZbNvuMjY0BAEBjYyMAAOzYsQMqlQrpq8WLF0N3d/dF1Vfr1q2DD3/4w6QfAKR//uM//gNWrFgBv//7vw+tra1w9dVXwz//8z9X6/v6+mBgYID0TyKRgNWrV18U/fOe97wHNm3aBPv37wcAgFdeeQVefPFFuPnmmwFA+gczlb7YvHkzJJNJWLFiRfUza9asAdM04aWXXqp7m2ebsbExMAwDkskkAEj/cM65rLbDw8Pgui6kUiny+1QqBXv37p2lVs0+nufBXXfdBddddx0sXboUAAAGBgbA7/dXJ/dvSaVSMDAwMAutrD+PPvoovPzyy7Bt27YJdRd7/xw6dAgeeOAB2LBhA3zlK1+Bbdu2wZ/92Z+B3++HtWvXVvvgTM/axdA/X/7ylyGTycDixYvBsixwXRfuvfdeuOOOOwAALvr+wUylLwYGBqC1tZXU27YNjY2NF11/FYtF+NKXvgSf+MQnqpltpX8o59zLh3Bm1q1bB6+//jq8+OKLs92Uc4Zjx47BF77wBXj66achGAzOdnPOOTzPgxUrVsDf/u3fAgDA1VdfDa+//jp873vfg7Vr185y62afH//4x/DDH/4QHnnkEbj88sth165dcNddd0FHR4f0j/COqVQq8Ad/8AeglIIHHnhgtptzznLOyS7Nzc1gWdYEj4TBwUFoa2ubpVbNLuvXr4cnn3wSnn32Wejs7Kz+vq2tDcrlMqTTafL5i6WvduzYAUNDQ3DNNdeAbdtg2zY899xz8J3vfAds24ZUKnVR9097eztcdtll5HdLliyBo0ePAgBU++Bifdb+/M//HL785S/Dxz/+cVi2bBn80R/9Edx9992wceNGAJD+wUylL9ra2mBoaIjUO44Do6OjF01//fbF48iRI/D0009Xdz0ApH8459zLh9/vh+XLl8OmTZuqv/M8DzZt2gS9vb2z2LL6o5SC9evXw+OPPw7PPPMM9PT0kPrly5eDz+cjfbVv3z44evToRdFXH/zgB+G1116DXbt2VX9WrFgBd9xxR/X4Yu6f6667boJr9v79+2Hu3LkAANDT0wNtbW2kfzKZDLz00ksXRf/k83kwTboEWpYFnucBgPQPZip90dvbC+l0Gnbs2FH9zDPPPAOe58Hq1avr3uZ689sXjwMHDsCvfvUraGpqIvUXe/9MYLYtXs/Eo48+qgKBgHr44YfV7t271Wc/+1mVTCbVwMDAbDetrvzJn/yJSiQS6te//rXq7++v/uTz+epnPve5z6nu7m71zDPPqO3bt6ve3l7V29s7i62eXbC3i1IXd/9s3bpV2bat7r33XnXgwAH1wx/+UIXDYfWv//qv1c/cd999KplMqp/+9Kfq1VdfVR/96EcvWFdSztq1a9WcOXOqrrY/+clPVHNzs/riF79Y/czF1D/j4+Nq586daufOnQoA1D/8wz+onTt3Vr01ptIXN910k7r66qvVSy+9pF588UW1cOHCC8aVtFb/lMtl9ZGPfER1dnaqXbt2kfW6VCpVz3Eh9890OSdfPpRS6h//8R9Vd3e38vv9atWqVWrLli2z3aS6AwBn/HnooYeqnykUCupP//RPVUNDgwqHw+r3fu/3VH9//+w1epbhLx8Xe//853/+p1q6dKkKBAJq8eLF6p/+6Z9Ived56mtf+5pKpVIqEAioD37wg2rfvn2z1Nr6kslk1Be+8AXV3d2tgsGgmj9/vvrqV79Kviwupv559tlnz7jerF27Vik1tb4YGRlRn/jEJ1Q0GlXxeFx96lOfUuPj47NwNzNPrf7p6+ubdL1+9tlnq+e4kPtnuhhKoXB+giAIgiAIZ5lzzuZDEARBEIQLG3n5EARBEAShrsjLhyAIgiAIdUVePgRBEARBqCvy8iEIgiAIQl2Rlw9BEARBEOqKvHwIgiAIglBX5OVDEARBEIS6Ii8fgiAIgiDUFXn5EARBEAShrsjLhyAIgiAIdeX/B3cb6aJoDXb/AAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GroundTruth:    cat  ship  ship plane\n"
     ]
    }
   ],
   "source": [
    "dataiter = iter(testloader)\n",
    "images, labels = next(dataiter)\n",
    "\n",
    "# print images\n",
    "imshow(torchvision.utils.make_grid(images))\n",
    "print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(labels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<All keys matched successfully>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 从本地加载模型\n",
    "\n",
    "net = Net()\n",
    "net.load_state_dict(torch.load(PATH))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 在测试集samples上推理\n",
    "\n",
    "outputs = net(images)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[-0.2471, -1.4958,  0.9304,  1.7359, -0.8227,  0.9629,  1.9376, -1.3136,\n",
      "         -1.1560, -1.1062],\n",
      "        [ 5.6997,  5.7385, -1.6788, -3.5362, -3.6856, -5.0423, -3.9435, -5.4323,\n",
      "          5.6568,  4.3602],\n",
      "        [ 2.5622,  3.0178, -1.1055, -1.3306, -2.2310, -2.4356, -2.8970, -2.6530,\n",
      "          3.5055,  2.3357],\n",
      "        [ 3.7219, -0.3204,  1.7751, -1.6752,  0.8287, -2.3000, -1.7717, -1.1870,\n",
      "          0.9378, -1.1467]], grad_fn=<AddmmBackward0>)\n"
     ]
    }
   ],
   "source": [
    "print(outputs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicted:   frog   car  ship plane\n"
     ]
    }
   ],
   "source": [
    "# torch.max()函数找到模型输出中每个样本得分最高的类别的索引。\n",
    "# torch.max()函数会返回两个Tensor，第一个Tensor是每行的最大值，第二个Tensor是最大值所在的索引（即预测的类别）。\n",
    "\n",
    "_, predicted = torch.max(outputs, 1)\n",
    "\n",
    "print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([6, 1, 8, 0])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "predicted"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy of the network on the 10000 test images: 52 %\n"
     ]
    }
   ],
   "source": [
    "# 评估模型在整个测试集上的性能，它计算了模型的预测正确率（accuracy），即预测正确的样本数量占总样本数量的比例。\n",
    "\n",
    "correct = 0 # 正确预测的数量\n",
    "total = 0 # 总的样本数量\n",
    "# 在评估模型的性能时，不需要更新模型的参数，也就不需要梯度。\n",
    "with torch.no_grad():\n",
    "    # 对测试数据集进行遍历\n",
    "    for data in testloader:\n",
    "        # 获取一批测试数据和对应的标签\n",
    "        images, labels = data\n",
    "        outputs = net(images)\n",
    "        # 找到模型输出中每个样本得分最高的类别的索引，即模型的预测结果。\n",
    "        _, predicted = torch.max(outputs.data, 1)\n",
    "        # 更新总的样本数量，labels.size(0)返回这个batch的样本数量。\n",
    "        total += labels.size(0)\n",
    "        # 更新正确预测的数量\n",
    "        # 首先(predicted==labels)返回一个布尔型的Tensor，表示预测结果和真实标签是否相等。\n",
    "        # 然后，使用sum()函数计算这个batch中预测正确的样本数量，最后使用item()函数将这个数量转换为Python的标量。\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "print('Accuracy of the network on the 10000 test images: %d %%' %\n",
    "      (100 * correct / total))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy of plane : 76 %\n",
      "Accuracy of   car : 50 %\n",
      "Accuracy of  bird : 49 %\n",
      "Accuracy of   cat : 16 %\n",
      "Accuracy of  deer : 49 %\n",
      "Accuracy of   dog : 47 %\n",
      "Accuracy of  frog : 62 %\n",
      "Accuracy of horse : 60 %\n",
      "Accuracy of  ship : 40 %\n",
      "Accuracy of truck : 74 %\n"
     ]
    }
   ],
   "source": [
    "# 评估模型在每个类别上的预测性能，它分别计算了模型在每个类别上的预测正确的样本数量和总的样本数量。\n",
    "\n",
    "class_correct = list(0. for i in range(10)) # 初始化每个类别的正确预测数量\n",
    "class_total = list(0. for i in range(10)) # 总的样本数量\n",
    "with torch.no_grad():\n",
    "    # 每一次循环，都会取出一个batch的数据。\n",
    "    for data in testloader:\n",
    "        # 获取一批测试数据和对应的标签\n",
    "        images, labels = data\n",
    "        outputs = net(images)\n",
    "        # 找到模型输出中每个样本得分最高的类别的索引，即模型的预测结果。\n",
    "        _, predicted = torch.max(outputs, 1)\n",
    "        # (predicted==labels)返回一个布尔型的Tensor，表示预测结果和真实标签是否相等。\n",
    "        # 然后，使用squeeze()函数移除这个Tensor中长度为1的维度，使其变成一个一维Tensor。\n",
    "        c = (predicted == labels).squeeze()\n",
    "        # 对一个batch中的四个样本进行处理\n",
    "        for i in range(4):\n",
    "            # 获取第i个样本的真实标签\n",
    "            label = labels[i]\n",
    "            # 如果模型对第i个样本的预测结果是正确的，那么c[i]的值为1，否则为0。\n",
    "            # 这里将c[i]的值累加到对应类别的正确预测数量中。\n",
    "            class_correct[label] += c[i].item()\n",
    "            # 更新对应类别的总的样本数量\n",
    "            class_total[label] += 1\n",
    "\n",
    "for i in range(10):\n",
    "    print('Accuracy of %5s : %2d %%' %\n",
    "          (classes[i], 100 * class_correct[i] / class_total[i]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. model inference with FastAPI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from fastapi import FastAPI\n",
    "from pydantic import BaseModel\n",
    "from torchvision.transforms import transforms\n",
    "import torch\n",
    "import base64\n",
    "import io\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "app = FastAPI()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 加载本地的模型\n",
    "\n",
    "model = Net()\n",
    "\n",
    "model.load_state_dict(torch.load(\"cifar_net.pth\"))\n",
    "\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "\n",
    "model.to(device)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=400, out_features=120, bias=True)\n",
       "  (fc2): Linear(in_features=120, out_features=84, bias=True)\n",
       "  (fc3): Linear(in_features=84, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 定义输入的数据模型\n",
    "\n",
    "class Item(BaseModel):\n",
    "    img_base64: str"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "@app.post(\"/predict\")\n",
    "def predict(item: Item):\n",
    "    # 对输入的base64图片进行解码和处理\n",
    "    # 将base64编码的字符串解码回原始的二进制图像数据\n",
    "    img = base64.b64decode(item.img_base64)\n",
    "    # 将二进制图像数据转换为一个PIL的Image对象\n",
    "    img = Image.open(io.BytesIO(img))\n",
    "    # 定义transform\n",
    "    transform = transforms.Compose([\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Resize((32,32)),\n",
    "        transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))\n",
    "    ])\n",
    "    # 图片预处理\n",
    "    img = transform(img).unsqueeze(0).to(device)\n",
    "    \n",
    "    # 进行推理\n",
    "    output = model(img)\n",
    "    \n",
    "    # 预测结果\n",
    "    # pred = output.argmax(dim=1, keepdim=True) \n",
    "    pred = torch.argmax(output, dim=1, keepdim=True)\n",
    "    \n",
    "    class_name = classes[pred]\n",
    "    \n",
    "    return {\"prediction\" : int(pred), \"class_name\": class_name}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7. test by requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import requests\n",
    "import base64\n",
    "import json\n",
    "from PIL import Image"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 打开图像\n",
    "\n",
    "with open(\"test_images/img01.jpg\", \"rb\") as f:\n",
    "    img_bytes = f.read()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 转换为 base64 编码\n",
    "\n",
    "img_b64 = base64.b64encode(img_bytes).decode(\"utf-8\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "ConnectionError",
     "evalue": "HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /predict (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x14eaec905d30>: Failed to establish a new connection: [Errno 111] Connection refused'))",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mConnectionRefusedError\u001b[39m                    Traceback (most recent call last)",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connection.py:203\u001b[39m, in \u001b[36mHTTPConnection._new_conn\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m    202\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m203\u001b[39m     sock = \u001b[43mconnection\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcreate_connection\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    204\u001b[39m \u001b[43m        \u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_dns_host\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mport\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    205\u001b[39m \u001b[43m        \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    206\u001b[39m \u001b[43m        \u001b[49m\u001b[43msource_address\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msource_address\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    207\u001b[39m \u001b[43m        \u001b[49m\u001b[43msocket_options\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msocket_options\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    208\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    209\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m socket.gaierror \u001b[38;5;28;01mas\u001b[39;00m e:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/util/connection.py:85\u001b[39m, in \u001b[36mcreate_connection\u001b[39m\u001b[34m(address, timeout, source_address, socket_options)\u001b[39m\n\u001b[32m     84\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m---> \u001b[39m\u001b[32m85\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m err\n\u001b[32m     86\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m     87\u001b[39m     \u001b[38;5;66;03m# Break explicitly a reference cycle\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/util/connection.py:73\u001b[39m, in \u001b[36mcreate_connection\u001b[39m\u001b[34m(address, timeout, source_address, socket_options)\u001b[39m\n\u001b[32m     72\u001b[39m     sock.bind(source_address)\n\u001b[32m---> \u001b[39m\u001b[32m73\u001b[39m \u001b[43msock\u001b[49m\u001b[43m.\u001b[49m\u001b[43mconnect\u001b[49m\u001b[43m(\u001b[49m\u001b[43msa\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m     74\u001b[39m \u001b[38;5;66;03m# Break explicitly a reference cycle\u001b[39;00m\n",
      "\u001b[31mConnectionRefusedError\u001b[39m: [Errno 111] Connection refused",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[31mNewConnectionError\u001b[39m                        Traceback (most recent call last)",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connectionpool.py:790\u001b[39m, in \u001b[36mHTTPConnectionPool.urlopen\u001b[39m\u001b[34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[39m\n\u001b[32m    789\u001b[39m \u001b[38;5;66;03m# Make the request on the HTTPConnection object\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m790\u001b[39m response = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_make_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    791\u001b[39m \u001b[43m    \u001b[49m\u001b[43mconn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    792\u001b[39m \u001b[43m    \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    793\u001b[39m \u001b[43m    \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    794\u001b[39m \u001b[43m    \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout_obj\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    795\u001b[39m \u001b[43m    \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    796\u001b[39m \u001b[43m    \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    797\u001b[39m \u001b[43m    \u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    798\u001b[39m \u001b[43m    \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    799\u001b[39m \u001b[43m    \u001b[49m\u001b[43mresponse_conn\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresponse_conn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    800\u001b[39m \u001b[43m    \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    801\u001b[39m \u001b[43m    \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    802\u001b[39m \u001b[43m    \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mresponse_kw\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    803\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    805\u001b[39m \u001b[38;5;66;03m# Everything went great!\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connectionpool.py:496\u001b[39m, in \u001b[36mHTTPConnectionPool._make_request\u001b[39m\u001b[34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[39m\n\u001b[32m    495\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m496\u001b[39m     \u001b[43mconn\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    497\u001b[39m \u001b[43m        \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    498\u001b[39m \u001b[43m        \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    499\u001b[39m \u001b[43m        \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    500\u001b[39m \u001b[43m        \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    501\u001b[39m \u001b[43m        \u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    502\u001b[39m \u001b[43m        \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    503\u001b[39m \u001b[43m        \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    504\u001b[39m \u001b[43m        \u001b[49m\u001b[43menforce_content_length\u001b[49m\u001b[43m=\u001b[49m\u001b[43menforce_content_length\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    505\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    507\u001b[39m \u001b[38;5;66;03m# We are swallowing BrokenPipeError (errno.EPIPE) since the server is\u001b[39;00m\n\u001b[32m    508\u001b[39m \u001b[38;5;66;03m# legitimately able to close the connection after sending a valid response.\u001b[39;00m\n\u001b[32m    509\u001b[39m \u001b[38;5;66;03m# With this behaviour, the received response is still readable.\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connection.py:395\u001b[39m, in \u001b[36mHTTPConnection.request\u001b[39m\u001b[34m(self, method, url, body, headers, chunked, preload_content, decode_content, enforce_content_length)\u001b[39m\n\u001b[32m    394\u001b[39m     \u001b[38;5;28mself\u001b[39m.putheader(header, value)\n\u001b[32m--> \u001b[39m\u001b[32m395\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mendheaders\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    397\u001b[39m \u001b[38;5;66;03m# If we're given a body we start sending that in chunks.\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/http/client.py:1322\u001b[39m, in \u001b[36mHTTPConnection.endheaders\u001b[39m\u001b[34m(self, message_body, encode_chunked)\u001b[39m\n\u001b[32m   1321\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m CannotSendHeader()\n\u001b[32m-> \u001b[39m\u001b[32m1322\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_send_output\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmessage_body\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mencode_chunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mencode_chunked\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/http/client.py:1081\u001b[39m, in \u001b[36mHTTPConnection._send_output\u001b[39m\u001b[34m(self, message_body, encode_chunked)\u001b[39m\n\u001b[32m   1080\u001b[39m \u001b[38;5;28;01mdel\u001b[39;00m \u001b[38;5;28mself\u001b[39m._buffer[:]\n\u001b[32m-> \u001b[39m\u001b[32m1081\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmsg\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   1083\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m message_body \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m   1084\u001b[39m \n\u001b[32m   1085\u001b[39m     \u001b[38;5;66;03m# create a consistent interface to message_body\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/http/client.py:1025\u001b[39m, in \u001b[36mHTTPConnection.send\u001b[39m\u001b[34m(self, data)\u001b[39m\n\u001b[32m   1024\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.auto_open:\n\u001b[32m-> \u001b[39m\u001b[32m1025\u001b[39m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mconnect\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   1026\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connection.py:243\u001b[39m, in \u001b[36mHTTPConnection.connect\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m    242\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mconnect\u001b[39m(\u001b[38;5;28mself\u001b[39m) -> \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m243\u001b[39m     \u001b[38;5;28mself\u001b[39m.sock = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_new_conn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    244\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m._tunnel_host:\n\u001b[32m    245\u001b[39m         \u001b[38;5;66;03m# If we're tunneling it means we're connected to our proxy.\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connection.py:218\u001b[39m, in \u001b[36mHTTPConnection._new_conn\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m    217\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m--> \u001b[39m\u001b[32m218\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m NewConnectionError(\n\u001b[32m    219\u001b[39m         \u001b[38;5;28mself\u001b[39m, \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33mFailed to establish a new connection: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00me\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m    220\u001b[39m     ) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01me\u001b[39;00m\n\u001b[32m    222\u001b[39m \u001b[38;5;66;03m# Audit hooks are only available in Python 3.8+\u001b[39;00m\n",
      "\u001b[31mNewConnectionError\u001b[39m: <urllib3.connection.HTTPConnection object at 0x14eaec905d30>: Failed to establish a new connection: [Errno 111] Connection refused",
      "\nThe above exception was the direct cause of the following exception:\n",
      "\u001b[31mMaxRetryError\u001b[39m                             Traceback (most recent call last)",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/adapters.py:667\u001b[39m, in \u001b[36mHTTPAdapter.send\u001b[39m\u001b[34m(self, request, stream, timeout, verify, cert, proxies)\u001b[39m\n\u001b[32m    666\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m667\u001b[39m     resp = \u001b[43mconn\u001b[49m\u001b[43m.\u001b[49m\u001b[43murlopen\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    668\u001b[39m \u001b[43m        \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    669\u001b[39m \u001b[43m        \u001b[49m\u001b[43murl\u001b[49m\u001b[43m=\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    670\u001b[39m \u001b[43m        \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    671\u001b[39m \u001b[43m        \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    672\u001b[39m \u001b[43m        \u001b[49m\u001b[43mredirect\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    673\u001b[39m \u001b[43m        \u001b[49m\u001b[43massert_same_host\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    674\u001b[39m \u001b[43m        \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    675\u001b[39m \u001b[43m        \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    676\u001b[39m \u001b[43m        \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    677\u001b[39m \u001b[43m        \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    678\u001b[39m \u001b[43m        \u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    679\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    681\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (ProtocolError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m err:\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/connectionpool.py:844\u001b[39m, in \u001b[36mHTTPConnectionPool.urlopen\u001b[39m\u001b[34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[39m\n\u001b[32m    842\u001b[39m     new_e = ProtocolError(\u001b[33m\"\u001b[39m\u001b[33mConnection aborted.\u001b[39m\u001b[33m\"\u001b[39m, new_e)\n\u001b[32m--> \u001b[39m\u001b[32m844\u001b[39m retries = \u001b[43mretries\u001b[49m\u001b[43m.\u001b[49m\u001b[43mincrement\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    845\u001b[39m \u001b[43m    \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43merror\u001b[49m\u001b[43m=\u001b[49m\u001b[43mnew_e\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_pool\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m_stacktrace\u001b[49m\u001b[43m=\u001b[49m\u001b[43msys\u001b[49m\u001b[43m.\u001b[49m\u001b[43mexc_info\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m[\u001b[49m\u001b[32;43m2\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[32m    846\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    847\u001b[39m retries.sleep()\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/urllib3/util/retry.py:515\u001b[39m, in \u001b[36mRetry.increment\u001b[39m\u001b[34m(self, method, url, response, error, _pool, _stacktrace)\u001b[39m\n\u001b[32m    514\u001b[39m     reason = error \u001b[38;5;129;01mor\u001b[39;00m ResponseError(cause)\n\u001b[32m--> \u001b[39m\u001b[32m515\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m MaxRetryError(_pool, url, reason) \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mreason\u001b[39;00m  \u001b[38;5;66;03m# type: ignore[arg-type]\u001b[39;00m\n\u001b[32m    517\u001b[39m log.debug(\u001b[33m\"\u001b[39m\u001b[33mIncremented Retry for (url=\u001b[39m\u001b[33m'\u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m'\u001b[39m\u001b[33m): \u001b[39m\u001b[38;5;132;01m%r\u001b[39;00m\u001b[33m\"\u001b[39m, url, new_retry)\n",
      "\u001b[31mMaxRetryError\u001b[39m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /predict (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x14eaec905d30>: Failed to establish a new connection: [Errno 111] Connection refused'))",
      "\nDuring handling of the above exception, another exception occurred:\n",
      "\u001b[31mConnectionError\u001b[39m                           Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[37]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# 方式-1 ： 启动服务后，发送 POST 请求\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m response = \u001b[43mrequests\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpost\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mhttp://localhost:8000/predict\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjson\u001b[49m\u001b[43m=\u001b[49m\u001b[43m \u001b[49m\u001b[43m{\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mimg_base64\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m:\u001b[49m\u001b[43m \u001b[49m\u001b[43mimg_b64\u001b[49m\u001b[43m}\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/api.py:115\u001b[39m, in \u001b[36mpost\u001b[39m\u001b[34m(url, data, json, **kwargs)\u001b[39m\n\u001b[32m    103\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mpost\u001b[39m(url, data=\u001b[38;5;28;01mNone\u001b[39;00m, json=\u001b[38;5;28;01mNone\u001b[39;00m, **kwargs):\n\u001b[32m    104\u001b[39m \u001b[38;5;250m    \u001b[39m\u001b[33mr\u001b[39m\u001b[33;03m\"\"\"Sends a POST request.\u001b[39;00m\n\u001b[32m    105\u001b[39m \n\u001b[32m    106\u001b[39m \u001b[33;03m    :param url: URL for the new :class:`Request` object.\u001b[39;00m\n\u001b[32m   (...)\u001b[39m\u001b[32m    112\u001b[39m \u001b[33;03m    :rtype: requests.Response\u001b[39;00m\n\u001b[32m    113\u001b[39m \u001b[33;03m    \"\"\"\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m115\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mpost\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mjson\u001b[49m\u001b[43m=\u001b[49m\u001b[43mjson\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/api.py:59\u001b[39m, in \u001b[36mrequest\u001b[39m\u001b[34m(method, url, **kwargs)\u001b[39m\n\u001b[32m     55\u001b[39m \u001b[38;5;66;03m# By using the 'with' statement we are sure the session is closed, thus we\u001b[39;00m\n\u001b[32m     56\u001b[39m \u001b[38;5;66;03m# avoid leaving sockets open which can trigger a ResourceWarning in some\u001b[39;00m\n\u001b[32m     57\u001b[39m \u001b[38;5;66;03m# cases, and look like a memory leak in others.\u001b[39;00m\n\u001b[32m     58\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m sessions.Session() \u001b[38;5;28;01mas\u001b[39;00m session:\n\u001b[32m---> \u001b[39m\u001b[32m59\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msession\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m=\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/sessions.py:589\u001b[39m, in \u001b[36mSession.request\u001b[39m\u001b[34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[39m\n\u001b[32m    584\u001b[39m send_kwargs = {\n\u001b[32m    585\u001b[39m     \u001b[33m\"\u001b[39m\u001b[33mtimeout\u001b[39m\u001b[33m\"\u001b[39m: timeout,\n\u001b[32m    586\u001b[39m     \u001b[33m\"\u001b[39m\u001b[33mallow_redirects\u001b[39m\u001b[33m\"\u001b[39m: allow_redirects,\n\u001b[32m    587\u001b[39m }\n\u001b[32m    588\u001b[39m send_kwargs.update(settings)\n\u001b[32m--> \u001b[39m\u001b[32m589\u001b[39m resp = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43msend_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    591\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m resp\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/sessions.py:703\u001b[39m, in \u001b[36mSession.send\u001b[39m\u001b[34m(self, request, **kwargs)\u001b[39m\n\u001b[32m    700\u001b[39m start = preferred_clock()\n\u001b[32m    702\u001b[39m \u001b[38;5;66;03m# Send the request\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m703\u001b[39m r = \u001b[43madapter\u001b[49m\u001b[43m.\u001b[49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m    705\u001b[39m \u001b[38;5;66;03m# Total elapsed time of the request (approximately)\u001b[39;00m\n\u001b[32m    706\u001b[39m elapsed = preferred_clock() - start\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/usr/local/miniconda3/lib/python3.12/site-packages/requests/adapters.py:700\u001b[39m, in \u001b[36mHTTPAdapter.send\u001b[39m\u001b[34m(self, request, stream, timeout, verify, cert, proxies)\u001b[39m\n\u001b[32m    696\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(e.reason, _SSLError):\n\u001b[32m    697\u001b[39m         \u001b[38;5;66;03m# This branch is for urllib3 v1.22 and later.\u001b[39;00m\n\u001b[32m    698\u001b[39m         \u001b[38;5;28;01mraise\u001b[39;00m SSLError(e, request=request)\n\u001b[32m--> \u001b[39m\u001b[32m700\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(e, request=request)\n\u001b[32m    702\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m ClosedPoolError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m    703\u001b[39m     \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(e, request=request)\n",
      "\u001b[31mConnectionError\u001b[39m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /predict (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x14eaec905d30>: Failed to establish a new connection: [Errno 111] Connection refused'))"
     ]
    }
   ],
   "source": [
    "# 方式-1 ： 启动服务后，发送 POST 请求\n",
    "\n",
    "response = requests.post(\"http://localhost:8000/predict\", json= {\"img_base64\": img_b64})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'prediction': 4, 'class_name': 'deer'}\n"
     ]
    }
   ],
   "source": [
    "# 方式-2 ： 直接通过函数传参实现\n",
    "\n",
    "item = Item(img_base64 = img_b64)\n",
    "\n",
    "\n",
    "print(predict(item))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
